﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Text;
using Microsoft.Xna.Framework;

namespace Shapes.Geometry
{
    /// <summary>
    /// an ellipsoid shape
    /// </summary>
    public sealed class Ellipse : Shape
    {
        //float f = 0;
        Vector2  _F1, _F2, _Center;

        internal float _MajorRadius, _MinorRadius;

        /// <summary>
        /// The value of the bigger radius (can be horizontal or vertical radius)
        /// </summary>
        public float MajorRadius { get { return _MajorRadius; } }
        /// <summary>
        /// the value of the smaller radius (can be horizontal or vertical radius)
        /// </summary>
        public float MinorRadius { get { return _MinorRadius; } }

        /// <summary>
        /// the horizontal radius value
        /// </summary>
        public float HorizontalRadius { get { return (Width > Height) ? _MajorRadius : _MinorRadius; } }
        /// <summary>
        /// the vertical radius value
        /// </summary>
        public float VerticalRadius { get { return(Width > Height) ? _MinorRadius : _MajorRadius; } }


        /// <summary>
        /// creates an ellipse shape
        /// </summary>
        /// <param name="horizontalRadius">the horizontal radius of the ellipse</param>
        /// <param name="verticalRadius">the vertical radius of the ellipse</param>
        public Ellipse(float horizontalRadius, float verticalRadius)
            : base()
        {
            ChangeRadius(horizontalRadius, verticalRadius);
            //SetCenter(Vector2.Zero);
        }

        /// <summary>
        /// creates an ellipse shape
        /// </summary>
        /// <param name="horizontalRadius">the horizontal radius of the ellipse</param>
        /// <param name="verticalRadius">the vertical radius of the ellipse</param>
        /// <param name="center">the position of the center of the ellipse</param>
        public Ellipse(Vector2 center, float horizontalRadius, float verticalRadius)
            : base()
        {
            ChangeRadius(horizontalRadius, verticalRadius);
            _Transform.Center = center;
            //SetCenter(center);
            //Position = center;
        }

        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public override GeometryType GetGeometryType()
        {
            return (_MajorRadius == _MinorRadius)
                ? GeometryType.Circle
                : GeometryType.Ellipse;
        }

        internal override bool IsPointInside(ref Vector2 point)
        {
            if (GetGeometryType() == GeometryType.Circle)
            {
                if (Vector2.Distance(point, _Center) <= _MajorRadius)
                {
                    return true;
                }
                return false;
            }

            if (GetPointDistance(ref point) <= 2 * _MajorRadius)
            {
                return true;
            }
            return false;
        }

        internal override float GetDistanceToEdge(ref Vector2 point)
        {
            if (GetGeometryType() == GeometryType.Circle)
            {
                Vector2 border = GetNearestPointOnEdge(ref point);
                return Vector2.Distance(point - _Center, border); 
            }
            return Vector2.Distance(point, GetNearestPointOnEdge(ref point));//Math.Abs((2 * _MajorRadius) - GetPointDistance(ref point)) * ((float)_MajorRadius / _MinorRadius);
        }

        internal override Vector2 GetNearestPointOnEdge(ref Vector2 point)
        {
            if (GetGeometryType() == GeometryType.Circle)
            {
                Vector2 pos = point - _Center;
                double angle = Math.Atan2(pos.Y, pos.X);
                return new Vector2((float)Math.Cos(angle) * _MajorRadius, (float)Math.Sin(angle) * _MajorRadius);
            }

            // TODO: find an algorithm for points on the border of an ellipse

            float scale = _MajorRadius / _MinorRadius;

            Vector2 p = new Vector2();
            if (Width > Height) // Major radius: Horizontal?
            {
                p.X = point.X - _Center.X;
                p.Y = (point.Y - _Center.Y) * scale;

                double angle = Math.Atan2(p.Y, p.X);
                p.X = (float)Math.Cos(angle) * _MajorRadius;
                p.Y = (float)Math.Sin(angle) * _MajorRadius;

                p.Y /= scale;
            }
            else
            {
                p.X = (point.X - _Center.X) * scale;
                p.Y = (point.Y - _Center.Y) ;

                double angle = Math.Atan2(p.Y, p.X);
                p.X = (float)Math.Cos(angle) * _MajorRadius;
                p.Y = (float)Math.Sin(angle) * _MajorRadius;

                p.X /= scale;
            }
            p.X += _Center.X;
            p.Y += _Center.Y;

            return p;
        }

        internal override Vector2 GetPositionFromT(float t)
        {
            t *= MathHelper.TwoPi;
            if (Width > Height)
            {
                return new Vector2(
                    _MajorRadius + (float)Math.Cos(t) * _MajorRadius,
                    _MinorRadius + (float)Math.Sin(t) * _MinorRadius);
            }
            else
            {
                return new Vector2(
                    _MinorRadius + (float)Math.Cos(t) * _MinorRadius,
                    _MajorRadius + (float)Math.Sin(t) * _MajorRadius);
            }
        }

        internal override float GetEdgePathValueFromPoint(ref Vector2 point)
        {
            float vScale;
            if (Width > Height)
                vScale = _MajorRadius / _MinorRadius;
            else
                vScale = _MinorRadius / _MajorRadius;

            float pointY = point.Y * vScale;

            float angle = (float)Math.Atan2(pointY - Center.Y, point.X - Center.X);

            return angle / MathHelper.TwoPi;
        }

        internal override Vector2 GetNormal(ref Vector2 position)
        {
            Vector2 center = _Center;
            if (GetGeometryType() != GeometryType.Circle)
            {
                float vScale;
                if (Width > Height)
                    vScale = _MajorRadius / _MinorRadius;
                else
                    vScale = _MinorRadius / _MajorRadius;

                //position.X /= vScale;
                position.Y *= vScale;
                center.Y *= vScale;
            }

            return  Vector2.Normalize(new Vector2(position.X - center.X, position.Y - center.Y));
        }
        internal override Vector2 GetTangent(ref Vector2 position)
        {
            Vector2 normal = GetNormal(ref position);
            return new Vector2(-normal.Y, normal.X);
        }

        private float GetPointDistance(ref Vector2 point)
        {
            return Vector2.Distance(_F1, point) + Vector2.Distance(_F2, point);
        }

        /// <summary>
        /// Changes the radius of the ellipse
        /// </summary>
        /// <param name="horizontalRadius">the horizontal radius of the ellipse</param>
        /// <param name="verticalRadius">the vertical radius of the ellipse</param>
        public void ChangeRadius(float horizontalRadius, float verticalRadius)
        {
            _Transform._Width = 2 * horizontalRadius;
            _Transform._Height = 2 * verticalRadius;
            _Center = new Vector2(horizontalRadius, verticalRadius);

            if (horizontalRadius == verticalRadius)
            {
                
                //_F1 = new Vector2(horizontalRadius, verticalRadius);
                _MajorRadius = _MinorRadius = horizontalRadius;
                _BorderLength = MathHelper.TwoPi * _MajorRadius;
            }
            else
            {
                _MinorRadius = (float)Math.Min(horizontalRadius, verticalRadius);
                _MajorRadius = (float)Math.Max(horizontalRadius, verticalRadius);


                float f = (float)Math.Sqrt(_MajorRadius * _MajorRadius - _MinorRadius * _MinorRadius);
                if (horizontalRadius > verticalRadius)
                {
                    _F1 = new Vector2(_Center.X - f, _Center.Y);
                    _F2 = new Vector2(_Center.X + f, _Center.Y);
                }
                else
                {
                    _F1 = new Vector2(_Center.X, _Center.Y - f);
                    _F2 = new Vector2(_Center.X, _Center.Y + f);
                }

                // the Ramanujan approach 
                float lambda = (_MajorRadius - _MinorRadius) / (_MajorRadius + _MinorRadius);
                _BorderLength = (_MajorRadius + _MinorRadius) * MathHelper.Pi
                    * (1 + (3 * lambda * lambda) / (10 + (float)Math.Sqrt(4 - 3 * lambda * lambda)));
            }

            CallOnChange();
        }

        /// <summary>
        /// Clones the Ellipse.
        /// </summary>
        /// <returns>an object with the type Ellipse</returns>
        public override object Clone()
        {
            Ellipse e = new Ellipse(Center, _Transform._Width / 2, _Transform._Height / 2);
            e._Transform = (Transformation2D)_Transform.Clone();
            return e;
        }
    }
}